//
//  Copyright RevenueCat Inc. All Rights Reserved.
//
//  Licensed under the MIT License (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      https://opensource.org/licenses/MIT
//
//  PurchasesType.swift
//
//  Created by Nacho Soto on 9/20/22.

import Foundation
import StoreKit

// swiftlint:disable file_length

/// Interface for ``Purchases``.
@objc(RCPurchasesType)
public protocol PurchasesType: AnyObject {

    /**
     * The ``appUserID`` used by ``Purchases``.
     * If not passed on initialization this will be generated and cached by ``Purchases``.
     */
    var appUserID: String { get }

    /**
     * The three-letter code representing the country or region
     * associated with the App Store storefront.
     * - Note: This property uses the ISO 3166-1 Alpha-3 country code representation.
     * 
     * #### Related articles
     * - ``Purchases/getStorefront(completion:)``
     * - ``Purchases/getStorefront()``
     */
    var storeFrontCountryCode: String? { get }

    /**
     * The ``appUserID`` used by ``Purchases``.
     * If not passed on initialization this will be generated and cached by ``Purchases``.
     */
    var isAnonymous: Bool { get }

    /** Controls if purchases should be made and transactions finished automatically by RevenueCat.
     * ``PurchasesAreCompletedBy/revenueCat`` by default.
     * - Warning: Setting this value to ``PurchasesAreCompletedBy/myApp``
     * will prevent the SDK from making purchases and finishing transactions.
     * More information on finishing transactions manually [is available here](https://rev.cat/finish-transactions).
     */
    var purchasesAreCompletedBy: PurchasesAreCompletedBy { get set }

    /**
     * Delegate for ``Purchases`` instance. The delegate is responsible for handling promotional product purchases and
     * changes to customer information.
     * - Note: this is not thread-safe.
     */
    var delegate: PurchasesDelegate? { get set }

    #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION

    /**
     * Obtain the storefront currently used by the Apple account. This will use StoreKit 2 first,
     * and if not possible, fallback to StoreKit 1. It will be `nil` if we can't obtain Apple's storefront.
     *
     * The `completion` block will be called with the latest Apple account storefront
     *
     * #### Related Articles
     * - ``Purchases/storeFrontCountryCode``
     * - ``Purchases/getStorefront()``
     */
    func getStorefront(completion: @escaping GetStorefrontBlock)

    /**
     * Obtain the storefront currently used by the Apple account. This will use StoreKit 2 first,
     * and if not possible, fallback to StoreKit 1. It will be `nil` if we can't obtain Apple's storefront.
     *
     * #### Related Articles
     * - ``Purchases/storeFrontCountryCode``
     * - ``Purchases/getStorefront(completion:)``
     */
    func getStorefront() async -> Storefront?

    /**
     * This function will log in the current user with an ``appUserID``.
     *
     * - Parameter appUserID: The ``appUserID`` that should be linked to the current user.
     *
     * The `completion` block will be called with the latest ``CustomerInfo`` and a `Bool` specifying
     * whether the user was created for the first time in the RevenueCat backend.
     *
     * RevenueCat provides a source of truth for a subscriber's status across different platforms.
     * To do this, each subscriber has an App User ID that uniquely identifies them within your application.
     *
     * User identity is one of the most important components of many mobile applications,
     * and it's extra important to make sure the subscription status RevenueCat is
     * tracking gets associated with the correct user.
     *
     * The Purchases SDK allows you to specify your own user identifiers or use anonymous identifiers
     * generated by RevenueCat. Some apps will use a combination
     * of their own identifiers and RevenueCat anonymous Ids - that's okay!
     *
     * #### Related Articles
     * - [Identifying Users](https://docs.revenuecat.com/docs/user-ids)
     * - ``Purchases/logOut(completion:)``
     * - ``Purchases/isAnonymous``
     * - ``Purchases/appUserID``
     */
    func logIn(_ appUserID: String, completion: @escaping (CustomerInfo?, Bool, PublicError?) -> Void)

    /**
     * This function will log in the current user with an ``appUserID``.
     *
     * - Parameter appUserID: The ``appUserID`` that should be linked to the current user.
     * - returns: A tuple of: the latest ``CustomerInfo`` and a `Bool` specifying
     * whether the user was created for the first time in the RevenueCat backend.
     *
     * RevenueCat provides a source of truth for a subscriber's status across different platforms.
     * To do this, each subscriber has an App User ID that uniquely identifies them within your application.
     *
     * User identity is one of the most important components of many mobile applications,
     * and it's extra important to make sure the subscription status RevenueCat is
     * tracking gets associated with the correct user.
     *
     * The Purchases SDK allows you to specify your own user identifiers or use anonymous identifiers
     * generated by RevenueCat. Some apps will use a combination
     * of their own identifiers and RevenueCat anonymous Ids - that's okay!
     *
     * #### Related Articles
     * - [Identifying Users](https://docs.revenuecat.com/docs/user-ids)
     * - ``Purchases/logOut()``
     * - ``Purchases/isAnonymous``
     * - ``Purchases/appUserID``
     */
    func logIn(_ appUserID: String) async throws -> (customerInfo: CustomerInfo, created: Bool)

    /**
     * Logs out the ``Purchases`` client, clearing the saved ``appUserID``.
     *
     * This will generate a random user id and save it in the cache.
     * If this method is called and the current user is anonymous, it will return an error.
     *
     * #### Related Articles
     * - [Identifying Users](https://docs.revenuecat.com/docs/user-ids)
     * - ``Purchases/logIn(_:)-arja``
     * - ``Purchases/isAnonymous``
     * - ``Purchases/appUserID``
     */
    func logOut(completion: ((CustomerInfo?, PublicError?) -> Void)?)

    /**
     * Logs out the ``Purchases`` client, clearing the saved ``appUserID``.
     *
     * This will generate a random user id and save it in the cache.
     * If this method is called and the current user is anonymous, it will return an error.
     *
     * #### Related Articles
     * - [Identifying Users](https://docs.revenuecat.com/docs/user-ids)
     * - ``Purchases/logIn(_:)-arja``
     * - ``Purchases/isAnonymous``
     * - ``Purchases/appUserID``
     */
    func logOut() async throws -> CustomerInfo

    /**
     * Get latest available customer  info.
     *
     * - Parameter completion: A completion block called when customer info is available and not stale.
     * Called immediately if ``CustomerInfo`` is cached. Customer info can be nil if an error occurred.
     */
    func getCustomerInfo(completion: @escaping ((CustomerInfo?, PublicError?) -> Void))

    /**
     * Get latest available customer info.
     *
     * - Parameter fetchPolicy: The behavior for what to do regarding caching.
     * - Parameter completion: A completion block called when customer info is available and not stale.
     */
    func getCustomerInfo(fetchPolicy: CacheFetchPolicy, completion: @escaping (CustomerInfo?, PublicError?) -> Void)

    /**
     * Get latest available customer info.
     *
     * #### Related Symbols
     * - ``Purchases/customerInfo(fetchPolicy:)``
     * - ``Purchases/customerInfoStream``
     */
    func customerInfo() async throws -> CustomerInfo

    /**
     * Get latest available customer info.
     *
     * - Parameter fetchPolicy: The behavior for what to do regarding caching.
     *
     * #### Related Symbols
     * - ``Purchases/customerInfoStream``
     */
    func customerInfo(fetchPolicy: CacheFetchPolicy) async throws -> CustomerInfo

    /**
     * The currently cached ``CustomerInfo`` if one is available.
     * This is synchronous, and therefore useful for contexts where an app needs a `CustomerInfo`
     * right away without waiting for a callback, like a SwiftUI view.
     *
     * This allows initializing state to ensure that UI can be loaded from the very first frame.
     */
    var cachedCustomerInfo: CustomerInfo? { get }

    #endif

    /**
     * Fetch the configured ``Offerings`` for this user.
     *
     * ``Offerings`` allows you to configure your in-app products
     * via RevenueCat and greatly simplifies management.
     *
     * ``Offerings`` will be fetched and cached on instantiation so that, by the time they are needed,
     * your prices are loaded for your purchase flow. Time is money.
     *
     * - Parameter completion: A completion block called when offerings are available.
     * Called immediately if offerings are cached. ``Offerings`` will be `nil` if an error occurred.
     *
     * #### Related Articles
     * -  [Displaying Products](https://docs.revenuecat.com/docs/displaying-products)
     */
    func getOfferings(completion: @escaping ((Offerings?, PublicError?) -> Void))

    /**
     * Fetch the configured ``Offerings`` for this user.
     *
     * ``Offerings`` allows you to configure your in-app products
     * via RevenueCat and greatly simplifies management.
     *
     * ``Offerings`` will be fetched and cached on instantiation so that, by the time they are needed,
     * your prices are loaded for your purchase flow. Time is money.
     *
     * #### Related Articles
     * -  [Displaying Products](https://docs.revenuecat.com/docs/displaying-products)
     */
    func offerings() async throws -> Offerings

    /**
     * The currently cached ``Offerings`` if available.
     * This is synchronous, and therefore useful for contexts where an app needs an instance of `Offerings`
     * right away without waiting for a callback, like a SwiftUI view.
     *
     * This allows initializing state to ensure that UI can be loaded from the very first frame.
     */
    var cachedOfferings: Offerings? { get }

    /**
     * Fetches the ``StoreProduct``s for your IAPs for given `productIdentifiers`.
     *
     * Use this method if you aren't using ``Purchases/getOfferings(completion:)``.
     * You should use ``Purchases/getOfferings(completion:)`` though.
     *
     * - Note: `completion` may be called without ``StoreProduct``s that you are expecting. This is usually caused by
     * iTunesConnect configuration errors. Ensure your IAPs have the "Ready to Submit" status in iTunesConnect.
     * Also ensure that you have an active developer program subscription and you have signed the latest paid
     * application agreements.
     * If you're having trouble, see:
     *  [App Store Connect In-App Purchase Configuration](https://rev.cat/how-to-configure-products)
     *
     * - Parameter productIdentifiers: A set of product identifiers for in-app purchases setup via
     * [AppStoreConnect](https://appstoreconnect.apple.com/)
     * This should be either hard coded in your application, from a file, or from a custom endpoint if you want
     * to be able to deploy new IAPs without an app update.
     * - Parameter completion: An `@escaping` callback that is called with the loaded products.
     * If the fetch fails for any reason it will return an empty array.
     */
    @objc(getProductsWithIdentifiers:completion:)
    func getProducts(_ productIdentifiers: [String], completion: @escaping ([StoreProduct]) -> Void)

    /**
     * Fetches the ``StoreProduct``s for your IAPs for given `productIdentifiers`.
     *
     * Use this method if you aren't using ``Purchases/getOfferings(completion:)``.
     * You should use ``Purchases/getOfferings(completion:)`` though.
     *
     * - Note: The result might not contain the ``StoreProduct``s that you are expecting. This is usually caused by
     * iTunesConnect configuration errors. Ensure your IAPs have the "Ready to Submit" status in iTunesConnect.
     * Also ensure that you have an active developer program subscription and you have signed the latest paid
     * application agreements.
     * If you're having trouble, see:
     * [App Store Connect In-App Purchase Configuration](https://rev.cat/how-to-configure-products)
     *
     * - Parameter productIdentifiers: A set of product identifiers for in-app purchases setup via
     * [AppStoreConnect](https://appstoreconnect.apple.com/)
     * This should be either hard coded in your application, from a file, or from a custom endpoint if you want
     * to be able to deploy new IAPs without an app update.
     */
    func products(_ productIdentifiers: [String]) async -> [StoreProduct]

    /**
     * Initiates a purchase of a ``StoreProduct``.
     *
     * Use this function if you are not using the ``Offerings`` system to purchase a ``StoreProduct``.
     * If you are using the ``Offerings`` system, use ``Purchases/purchase(package:completion:)`` instead.
     *
     * - Important: Call this method when a user has decided to purchase a product.
     * Only call this in direct response to user input.
     *
     * From here ``Purchases`` will handle the purchase with `StoreKit` and call the ``PurchaseCompletedBlock``.
     *
     * - Note: You do not need to finish the transaction yourself in the completion callback, Purchases will
     * handle this for you.
     *
     * - Parameter product: The ``StoreProduct`` the user intends to purchase.
     * - Parameter completion: A completion block that is called when the purchase completes.
     *
     * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``.
     *
     * If the purchase was not successful, there will be an `NSError`.
     *
     * If the user cancelled, `userCancelled` will be `true`.
     */
    @objc(purchaseProduct:withCompletion:)
    func purchase(product: StoreProduct, completion: @escaping PurchaseCompletedBlock)

    /**
     * Initiates a purchase of a ``StoreProduct``.
     *
     * Use this function if you are not using the ``Offerings`` system to purchase a ``StoreProduct``.
     * If you are using the ``Offerings`` system, use ``Purchases/purchase(package:completion:)`` instead.
     *
     * - Important: Call this method when a user has decided to purchase a product.
     * Only call this in direct response to user input.
     *
     * From here ``Purchases`` will handle the purchase with `StoreKit` and return ``PurchaseResultData``.
     *
     * - Note: You do not need to finish the transaction yourself after this, ``Purchases`` will
     * handle this for you.
     *
     * - Parameter product: The ``StoreProduct`` the user intends to purchase.
     *
     * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing
     *
     * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful.
     * If the user cancelled the purchase, `userCancelled` will be `true`.
     */
    func purchase(product: StoreProduct) async throws -> PurchaseResultData

    /**
     * Initiates a purchase of a ``Package``.
     *
     * - Important: Call this method when a user has decided to purchase a product.
     * Only call this in direct response to user input.

     * From here ``Purchases`` will handle the purchase with `StoreKit` and call the ``PurchaseCompletedBlock``.
     *
     * - Note: You do not need to finish the transaction yourself in the completion callback, Purchases will
     * handle this for you.
     *
     * - Parameter package: The ``Package`` the user intends to purchase
     * - Parameter completion: A completion block that is called when the purchase completes.
     *
     * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``.
     *
     * If the purchase was not successful, there will be an `NSError`.
     *
     * If the user cancelled, `userCancelled` will be `true`.
     */
    @objc(purchasePackage:withCompletion:)
    func purchase(package: Package, completion: @escaping PurchaseCompletedBlock)

    /**
     * Initiates a purchase of a ``Package``.
     *
     * - Important: Call this method when a user has decided to purchase a product.
     * Only call this in direct response to user input.
     *
     * From here ``Purchases`` will handle the purchase with `StoreKit` and return ``PurchaseResultData``.
     *
     * - Note: You do not need to finish the transaction yourself after this, Purchases will
     * handle this for you.
     *
     * - Parameter package: The ``Package`` the user intends to purchase
     *
     * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing
     *
     * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful.
     * If the user cancelled the purchase, `userCancelled` will be `true`.
     */
    func purchase(package: Package) async throws -> PurchaseResultData

    #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION

    /**
     * Initiates a purchase.
     *
     * - Important: Call this method when a user has decided to purchase a product.
     * Only call this in direct response to user input.
     *
     * From here ``Purchases`` will handle the purchase with `StoreKit` and call the ``PurchaseCompletedBlock``.
     *
     * - Note: You do not need to finish the transaction yourself in the completion callback, Purchases will
     * handle this for you.
     *
     * - Parameter params: The ``PurchaseParams`` instance with the configuration options for this purchase.
     * Check the ``PurchaseParams`` documentation for more information.
     *
     * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``.
     *
     * If the purchase was not successful, there will be an `NSError`.
     *
     * If the user cancelled, `userCancelled` will be `true`.
     */
    @objc(purchaseWithParams:completion:)
    func purchase(_ params: PurchaseParams, completion: @escaping PurchaseCompletedBlock)

    /**
     * Initiates a purchase.
     *
     * - Important: Call this method when a user has decided to purchase a product.
     * Only call this in direct response to user input.
     *
     * From here ``Purchases`` will handle the purchase with `StoreKit` and return ``PurchaseResultData``.
     *
     * - Note: You do not need to finish the transaction yourself after this, ``Purchases`` will
     * handle this for you.
     *
     * - Parameter params: The ``PurchaseParams`` instance with extra configuration options for this purchase.
     * Check the ``PurchaseParams`` documentation for more information.
     *
     * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing
     *
     * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful.
     * If the user cancelled the purchase, `userCancelled` will be `true`.
     */
    func purchase(_ params: PurchaseParams) async throws -> PurchaseResultData

    /**
     * Invalidates the cache for customer information.
     *
     * Most apps will not need to use this method; invalidating the cache can leave your app in an invalid state.
     * Refer to
     * [Get User Information](https://docs.revenuecat.com/docs/purchaserinfo#section-get-user-information)
     * for more information on using the cache properly.
     *
     * This is useful for cases where customer information might have been updated outside of the app, like if a
     * promotional subscription is granted through the RevenueCat dashboard.
     */
    func invalidateCustomerInfoCache()

    /**
     * This method will post all purchases associated with the current App Store account to RevenueCat and become
     * associated with the current ``appUserID``. If the receipt is being used by an existing user, the current
     * ``appUserID`` will be aliased together with the ``appUserID`` of the existing user.
     *  Going forward, either ``appUserID`` will be able to reference the same user.
     *
     * You shouldn't use this method if you have your own account system. In that case "restoration" is provided
     * by your app passing the same ``appUserID`` used to purchase originally.
     *
     * - Note: This may force your users to enter the App Store password so should only be performed on request of
     * the user. Typically with a button in settings or near your purchase UI. Use
     * ``Purchases/syncPurchases(completion:)`` if you need to restore transactions programmatically.
     *
     * - Warning: Receiving a ``CustomerInfo`` instead of an error does not imply that the user has any
     * entitlements, simply that the process was successful. You must verify the ``CustomerInfo/entitlements``
     * to confirm that they are active.
     */
    func restorePurchases(completion: ((CustomerInfo?, PublicError?) -> Void)?)

    /**
     * This method will post all purchases associated with the current App Store account to RevenueCat and become
     * associated with the current ``appUserID``. If the receipt is being used by an existing user, the current
     * ``appUserID`` will be aliased together with the ``appUserID`` of the existing user.
     *  Going forward, either ``appUserID`` will be able to reference the same user.
     *
     * You shouldn't use this method if you have your own account system. In that case "restoration" is provided
     * by your app passing the same ``appUserID`` used to purchase originally.
     *
     * - Note: This may force your users to enter the App Store password so should only be performed on request of
     * the user. Typically with a button in settings or near your purchase UI. Use
     * ``Purchases/syncPurchases(completion:)`` if you need to restore transactions programmatically.
     *
     * - Warning: Receiving a ``CustomerInfo`` instead of an error does not imply that the user has any
     * entitlements, simply that the process was successful. You must verify the ``CustomerInfo/entitlements``
     * to confirm that they are active.
     */
    func restorePurchases() async throws -> CustomerInfo

    /**
     * This method will post all purchases associated with the current App Store account to RevenueCat and
     * become associated with the current ``appUserID``.
     *
     * If the receipt is being used by an existing user, the current ``appUserID`` will be aliased together with
     * the ``appUserID`` of the existing user.
     * Going forward, either ``appUserID`` will be able to reference the same user.
     *
     * - Warning: This function should only be called if you're not calling any purchase method.
     *
     * - Note: This method will not trigger a login prompt from App Store. However, if the receipt currently
     * on the device does not contain subscriptions, but the user has made subscription purchases, this method
     * won't be able to restore them. Use ``Purchases/restorePurchases(completion:)`` to cover those cases.
     */
    func syncPurchases(completion: ((CustomerInfo?, PublicError?) -> Void)?)

    /**
     * This method will post all purchases associated with the current App Store account to RevenueCat and
     * become associated with the current ``appUserID``.
     *
     * If the receipt is being used by an existing user, the current ``appUserID`` will be aliased together with
     * the ``appUserID`` of the existing user.
     * Going forward, either ``appUserID`` will be able to reference the same user.
     *
     * - Warning: This function should only be called if you're not calling any purchase method.
     *
     * - Note: This method will not trigger a login prompt from App Store. However, if the receipt currently
     * on the device does not contain subscriptions, but the user has made subscription purchases, this method
     * won't be able to restore them. Use ``Purchases/restorePurchases(completion:)`` to cover those cases.
     */
    func syncPurchases() async throws -> CustomerInfo

    /**
     * Initiates a purchase of a ``StoreProduct`` with a ``PromotionalOffer``.
     *
     * Use this function if you are not using the Offerings system to purchase a ``StoreProduct`` with an
     * applied ``PromotionalOffer``.
     * If you are using the Offerings system, use ``Purchases/purchase(package:promotionalOffer:completion:)`` instead.
     *
     * - Important: Call this method when a user has decided to purchase a product with an applied discount.
     * Only call this in direct response to user input.
     *
     * From here ``Purchases`` will handle the purchase with `StoreKit` and call the ``PurchaseCompletedBlock``.
     *
     * - Note: You do not need to finish the transaction yourself in the completion callback, Purchases will handle
     * this for you.
     *
     * - Parameter product: The ``StoreProduct`` the user intends to purchase.
     * - Parameter promotionalOffer: The ``PromotionalOffer`` to apply to the purchase.
     * - Parameter completion: A completion block that is called when the purchase completes.
     *
     * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``.
     * If the purchase was not successful, there will be an `NSError`.
     * If the user cancelled, `userCancelled` will be `true`.
     *
     * #### Related Symbols
     * - ``StoreProduct/discounts``
     * - ``StoreProduct/eligiblePromotionalOffers()``
     * - ``Purchases/promotionalOffer(forProductDiscount:product:)``
     */
    @objc(purchaseProduct:withPromotionalOffer:completion:)
    func purchase(product: StoreProduct,
                  promotionalOffer: PromotionalOffer,
                  completion: @escaping PurchaseCompletedBlock)

    /**
     * Use this function if you are not using the Offerings system to purchase a ``StoreProduct`` with an
     * applied ``PromotionalOffer``.
     * If you are using the Offerings system, use ``Purchases/purchase(package:promotionalOffer:completion:)`` instead.
     *
     * Call this method when a user has decided to purchase a product with an applied discount.
     * Only call this in direct response to user input.
     *
     * From here ``Purchases`` will handle the purchase with `StoreKit` and return ``PurchaseResultData``.
     *
     * - Note: You do not need to finish the transaction yourself after this, Purchases will handle
     * this for you.
     *
     * - Parameter product: The ``StoreProduct`` the user intends to purchase
     * - Parameter promotionalOffer: The ``PromotionalOffer`` to apply to the purchase
     *
     * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing
     *
     * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful.
     * If the user cancelled the purchase, `userCancelled` will be `true`.
     */
    func purchase(product: StoreProduct, promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData

    /**
     * Purchase the passed ``Package``.
     * Call this method when a user has decided to purchase a product with an applied discount. Only call this in
     * direct response to user input. From here ``Purchases`` will handle the purchase with `StoreKit` and call the
     * ``PurchaseCompletedBlock``.
     *
     * - Note: You do not need to finish the transaction yourself in the completion callback, Purchases will handle
     * this for you.
     *
     * - Parameter package: The ``Package`` the user intends to purchase
     * - Parameter promotionalOffer: The ``PromotionalOffer`` to apply to the purchase
     * - Parameter completion: A completion block that is called when the purchase completes.
     *
     * If the purchase was successful there will be a ``StoreTransaction`` and a ``CustomerInfo``.
     * If the purchase was not successful, there will be an `NSError`.
     * If the user cancelled, `userCancelled` will be `true`.
     */
    @objc(purchasePackage:withPromotionalOffer:completion:)
    func purchase(package: Package,
                  promotionalOffer: PromotionalOffer,
                  completion: @escaping PurchaseCompletedBlock)

    /**
     * Purchase the passed ``Package``.
     * Call this method when a user has decided to purchase a product with an applied discount. Only call this in
     * direct response to user input. From here ``Purchases`` will handle the purchase with `StoreKit` and return
     * ``PurchaseResultData``.
     *
     * - Note: You do not need to finish the transaction yourself after this, Purchases will handle
     * this for you.
     *
     * - Parameter package: The ``Package`` the user intends to purchase
     * - Parameter promotionalOffer: The ``PromotionalOffer`` to apply to the purchase
     *
     * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while purchasing
     *
     * - Returns: A tuple with ``StoreTransaction`` and a ``CustomerInfo`` if the purchase was successful.
     * If the user cancelled the purchase, `userCancelled` will be `true`.
     */
    func purchase(package: Package, promotionalOffer: PromotionalOffer) async throws -> PurchaseResultData

    /**
     * Computes whether or not a user is eligible for the introductory pricing period of a given product.
     * You should use this method to determine whether or not you show the user the normal product price or
     * the introductory price. This also applies to trials (trials are considered a type of introductory pricing).
     * [iOS Introductory  Offers](https://docs.revenuecat.com/docs/ios-subscription-offers).
     *
     * - Note: If you're looking to use Promotional Offers instead,
     * use ``Purchases/getPromotionalOffer(forProductDiscount:product:completion:)``.
     *
     * - Note: Subscription groups are automatically collected for determining eligibility. If RevenueCat can't
     * definitively compute the eligibility, most likely because of missing group information, it will return
     * ``IntroEligibilityStatus/unknown``. The best course of action on unknown status is to display the non-intro
     * pricing, to not create a misleading situation. To avoid this, make sure you are testing with the latest
     * version of iOS so that the subscription group can be collected by the SDK.
     *
     *
     * - Parameter productIdentifiers: Array of product identifiers for which you want to compute eligibility
     * - Parameter receiveEligibility: A block that receives a dictionary of `product_id` -> ``IntroEligibility``.
     *
     * ### Related symbols
     * - ``Purchases/checkTrialOrIntroDiscountEligibility(product:completion:)``
     */
    @objc(checkTrialOrIntroDiscountEligibility:completion:)
    func checkTrialOrIntroDiscountEligibility(
        productIdentifiers: [String],
        completion receiveEligibility: @escaping ([String: IntroEligibility]) -> Void
    )

    /**
     * Computes whether or not a user is eligible for the introductory pricing period of a given product.
     * You should use this method to determine whether or not you show the user the normal product price or
     * the introductory price. This also applies to trials (trials are considered a type of introductory pricing).
     * [iOS Introductory  Offers](https://docs.revenuecat.com/docs/ios-subscription-offers).
     *
     * - Note: If you're looking to use Promotional Offers instead,
     * use ``Purchases/getPromotionalOffer(forProductDiscount:product:completion:)``.
     *
     * - Note: Subscription groups are automatically collected for determining eligibility. If RevenueCat can't
     * definitively compute the eligibility, most likely because of missing group information, it will return
     * ``IntroEligibilityStatus/unknown``. The best course of action on unknown status is to display the non-intro
     * pricing, to not create a misleading situation. To avoid this, make sure you are testing with the latest
     * version of iOS so that the subscription group can be collected by the SDK.
     *
     * - Parameter productIdentifiers: Array of product identifiers for which you want to compute eligibility
     *
     * ### Related symbols
     * - ``Purchases/checkTrialOrIntroDiscountEligibility(product:)``
     */
    func checkTrialOrIntroDiscountEligibility(productIdentifiers: [String]) async -> [String: IntroEligibility]

    /**
     * Computes whether or not a user is eligible for the introductory pricing period of a given product.
     * You should use this method to determine whether or not you show the user the normal product price or
     * the introductory price. This also applies to trials (trials are considered a type of introductory pricing).
     * [iOS Introductory  Offers](https://docs.revenuecat.com/docs/ios-subscription-offers).
     *
     * - Note: If you're looking to use Promotional Offers instead,
     * use ``Purchases/getPromotionalOffer(forProductDiscount:product:completion:)``.
     *
     * - Note: Subscription groups are automatically collected for determining eligibility. If RevenueCat can't
     * definitively compute the eligibility, most likely because of missing group information, it will return
     * ``IntroEligibilityStatus/unknown``. The best course of action on unknown status is to display the non-intro
     * pricing, to not create a misleading situation. To avoid this, make sure you are testing with the latest
     * version of iOS so that the subscription group can be collected by the SDK.
     *
     *
     * - Parameter product: The ``StoreProduct``  for which you want to compute eligibility.
     * - Parameter completion: A block that receives an ``IntroEligibilityStatus``.
     *
     * ### Related symbols
     * - ``Purchases/checkTrialOrIntroDiscountEligibility(productIdentifiers:completion:)``
     */
    @objc(checkTrialOrIntroDiscountEligibilityForProduct:completion:)
    func checkTrialOrIntroDiscountEligibility(
        product: StoreProduct,
        completion: @escaping (IntroEligibilityStatus) -> Void
    )

    /**
     * Computes whether or not a user is eligible for the introductory pricing period of a given product.
     * You should use this method to determine whether or not you show the user the normal product price or
     * the introductory price. This also applies to trials (trials are considered a type of introductory pricing).
     * [iOS Introductory  Offers](https://docs.revenuecat.com/docs/ios-subscription-offers).
     *
     * - Note: If you're looking to use Promotional Offers instead,
     * use ``Purchases/getPromotionalOffer(forProductDiscount:product:completion:)``.
     *
     * - Note: Subscription groups are automatically collected for determining eligibility. If RevenueCat can't
     * definitively compute the eligibility, most likely because of missing group information, it will return
     * ``IntroEligibilityStatus/unknown``. The best course of action on unknown status is to display the non-intro
     * pricing, to not create a misleading situation. To avoid this, make sure you are testing with the latest
     * version of iOS so that the subscription group can be collected by the SDK.
     *
     *
     * - Parameter product: The ``StoreProduct``  for which you want to compute eligibility.
     *
     * ### Related symbols
     * - ``Purchases/checkTrialOrIntroDiscountEligibility(productIdentifiers:)``
     */
    func checkTrialOrIntroDiscountEligibility(product: StoreProduct) async -> IntroEligibilityStatus

    /**
     * Use this method to fetch ``PromotionalOffer``
     *  to use in ``Purchases/purchase(package:promotionalOffer:)``
     *  or ``Purchases/purchase(product:promotionalOffer:)``.
     * [iOS Promotional Offers](https://docs.revenuecat.com/docs/ios-subscription-offers#promotional-offers).
     * - Note: If you're looking to use free trials or Introductory Offers instead,
     * use ``Purchases/checkTrialOrIntroDiscountEligibility(productIdentifiers:completion:)``.
     *
     * - Parameter discount: The ``StoreProductDiscount`` to apply to the product.
     * - Parameter product: The ``StoreProduct`` the user intends to purchase.
     * - Parameter completion: A completion block that is called when the ``PromotionalOffer`` is returned.
     * If it was not successful, there will be an `Error`.
     */
    @objc(getPromotionalOfferForProductDiscount:withProduct:withCompletion:)
    func getPromotionalOffer(forProductDiscount discount: StoreProductDiscount,
                             product: StoreProduct,
                             completion: @escaping ((PromotionalOffer?, PublicError?) -> Void))

    /**
     * Use this method to find eligibility for this user for
     * [iOS Promotional Offers](https://docs.revenuecat.com/docs/ios-subscription-offers#promotional-offers).
     * - Note: If you're looking to use free trials or Introductory Offers instead,
     * use ``Purchases/checkTrialOrIntroDiscountEligibility(productIdentifiers:completion:)``.
     *
     * - Parameter discount: The ``StoreProductDiscount`` to apply to the product.
     * - Parameter product: The ``StoreProduct`` the user intends to purchase.
     */
    func promotionalOffer(forProductDiscount discount: StoreProductDiscount,
                          product: StoreProduct) async throws -> PromotionalOffer

    /// Finds the subset of ``StoreProduct/discounts`` that's eligible for the current user.
    ///
    /// - Parameter product: the product to filter discounts from.
    /// - Note: if checking for eligibility for a `StoreProductDiscount` fails (for example, if network is down),
    ///   that discount will fail silently and be considered not eligible.
    /// #### Related Symbols
    /// - ``Purchases/promotionalOffer(forProductDiscount:product:)``
    /// - ``StoreProduct/eligiblePromotionalOffers()``
    /// - ``StoreProduct/discounts``
    func eligiblePromotionalOffers(forProduct product: StoreProduct) async -> [PromotionalOffer]

    /**
     * Returns the win-back offers that the subscriber is eligible for on the provided product.
     *
     * - Parameter product: The product to check for eligible win-back offers.
     * - Parameter completion: A completion block that is called with the eligible win-back
     * offers for the provided product.
     * - Important: Win-back offers are only supported when the SDK is running with StoreKit 2 enabled.
     */
    @available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
    func eligibleWinBackOffers(
        forProduct product: StoreProduct,
        completion: @escaping @Sendable ([WinBackOffer]?, PublicError?) -> Void
    )

    /**
     * Returns the win-back offers that the subscriber is eligible for on the provided package.
     *
     * - Parameter package: The package to check for eligible win-back offers.
     * - Parameter completion: A completion block that is called with the eligible win-back
     * offers for the provided product.
     * - Important: Win-back offers are only supported when the SDK is running with StoreKit 2 enabled.
     */
    @available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
    func eligibleWinBackOffers(
        forPackage package: Package,
        completion: @escaping @Sendable ([WinBackOffer]?, PublicError?) -> Void
    )

    #endif

    #if os(iOS) || VISION_OS

    /**
     * Presents a refund request sheet in the current window scene for
     * the latest transaction associated with the `productID`
     *
     * - Parameter productID: The `productID` to begin a refund request for.
     * If the request was successful, there will be a ``RefundRequestStatus``.
     * Keep in mind the status could be ``RefundRequestStatus/userCancelled``
     *
     * - throws: If the request was unsuccessful, there will be an `Error` and `RefundRequestStatus.error`.
     */
    @available(iOS 15.0, *)
    @available(macOS, unavailable)
    @available(watchOS, unavailable)
    @available(tvOS, unavailable)
    @objc(beginRefundRequestForProduct:completion:)
    func beginRefundRequest(forProduct productID: String) async throws -> RefundRequestStatus

    /**
     * Presents a refund request sheet in the current window scene for
     * the latest transaction associated with the entitlement ID.
     *
     * - Parameter entitlementID: The entitlementID to begin a refund request for.
     * - returns ``RefundRequestStatus``: The status of the refund request.
     * Keep in mind the status could be ``RefundRequestStatus/userCancelled``
     *
     * - throws: If the request was unsuccessful or the entitlement could not be found, an `Error` will be thrown.
     */
    @available(iOS 15.0, *)
    @available(macOS, unavailable)
    @available(watchOS, unavailable)
    @available(tvOS, unavailable)
    @objc(beginRefundRequestForEntitlement:completion:)
    func beginRefundRequest(forEntitlement entitlementID: String) async throws -> RefundRequestStatus

    /**
     * Presents a refund request sheet in the current window scene for
     * the latest transaction associated with the active entitlement.
     *
     * - returns ``RefundRequestStatus``: The status of the refund request.
     * Keep in mind the status could be ``RefundRequestStatus/userCancelled``
     *
     *- throws: If the request was unsuccessful, no active entitlements could be found for the user,
     * or multiple active entitlements were found for the user, an `Error` will be thrown.
     *
     *- important: This method should only be used if your user can only
     * have a single active entitlement at a given time. If a user could have more than one entitlement at a time,
     * use ``Purchases/beginRefundRequest(forEntitlement:)`` instead.
     */
    @available(iOS 15.0, *)
    @available(macOS, unavailable)
    @available(watchOS, unavailable)
    @available(tvOS, unavailable)
    @objc(beginRefundRequestForActiveEntitlementWithCompletion:)
    func beginRefundRequestForActiveEntitlement() async throws -> RefundRequestStatus

    #endif

    /**
     * Displays a sheet that enables users to redeem subscription offer codes that you generated in App Store Connect.
     *
     * - Important: Even though the docs in `SKPaymentQueue.presentCodeRedemptionSheet`
     * say that it's available on Catalyst 14.0, there is a note:
     * "`This function doesn’t affect Mac apps built with Mac Catalyst.`"
     * when, in fact, it crashes when called both from Catalyst and also when running as "Designed for iPad".
     * This is why RevenueCat's SDK makes it unavailable in mac catalyst.
     */
    @available(iOS 14.0, *)
    @available(watchOS, unavailable)
    @available(tvOS, unavailable)
    @available(macOS, unavailable)
    @available(macCatalyst, unavailable)
    func presentCodeRedemptionSheet()

    #if os(iOS) || targetEnvironment(macCatalyst) || VISION_OS
    /**
     * Displays price consent sheet if needed. You only need to call this manually if you implement
     * ``PurchasesDelegate/shouldShowPriceConsent`` and return false at some point.
     *
     * You may want to delay showing the sheet if it would interrupt your user’s interaction in your app. You can do
     * this by implementing ``PurchasesDelegate/shouldShowPriceConsent``.
     *
     * In most cases, you don't _*typically*_ implement ``PurchasesDelegate/shouldShowPriceConsent``, therefore,
     * you won't need to call this.
     *
     * ### Related Symbols
     * - ``SKPaymentQueue/showPriceConsentIfNeeded()`
     *
     * ### Related Articles
     * - [Apple Documentation](https://rev.cat/testing-promoted-in-app-purchases)
     */
    @available(iOS 13.4, macCatalyst 13.4, *)
    @objc func showPriceConsentIfNeeded()
    #endif

    #if os(iOS) || os(macOS) || VISION_OS

    /**
     * Use this function to open the manage subscriptions page.
     *
     * - Parameter completion: A completion block that will be called when the modal is opened,
     * not when it's actually closed. This is because of an undocumented change in StoreKit's behavior
     * between iOS 15.0 and 15.2, where 15.0 would return when the modal was closed, and 15.2 returns
     * when the modal is opened.
     *
     * If the manage subscriptions page can't be opened, the ``CustomerInfo/managementURL`` in
     * the ``CustomerInfo`` will be opened. If ``CustomerInfo/managementURL`` is not available,
     * the App Store's subscription management section will be opened.
     *
     * The `completion` block will be called when the modal is opened, not when it's actually closed.
     * This is because of an undocumented change in StoreKit's behavior between iOS 15.0 and 15.2,
     * where 15.0 would return when the modal was closed,
     * and 15.2 returns when the modal is opened.
     */
    @available(watchOS, unavailable)
    @available(tvOS, unavailable)
    @available(iOS 13.0, macOS 10.15, *)
    @objc func showManageSubscriptions(completion: @escaping (PublicError?) -> Void)

    /**
     * Use this function to open the manage subscriptions modal.
     *
     * - throws: an `Error` will be thrown if the current window scene couldn't be opened,
     * or the ``CustomerInfo/managementURL`` couldn't be obtained.
     * If the manage subscriptions page can't be opened, the ``CustomerInfo/managementURL`` in
     * the ``CustomerInfo`` will be opened. If ``CustomerInfo/managementURL`` is not available,
     * the App Store's subscription management section will be opened.
     */
    @available(watchOS, unavailable)
    @available(tvOS, unavailable)
    @available(iOS 13.0, macOS 10.15, *)
    func showManageSubscriptions() async throws

    #endif

    #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION

    /**
     * ``Attribution`` object that is responsible for all explicit attribution APIs
     * as well as subscriber attributes that RevenueCat offers.
     *
     * #### Example:
     *
     * ```swift
     * Purchases.shared.attribution.setEmail(“nobody@example.com”)
     * ```
     *
     * #### Related Articles
     * - [Subscriber Attribution](https://docs.revenuecat.com/docs/subscriber-attributes)
     * - ``Attribution``
     */
    var attribution: Attribution { get }

    /**
     * Syncs subscriber attributes and then fetches the configured offerings for this user. This method is intended to
     * be called when using Targeting Rules with Custom Attributes. Any subscriber attributes should be set before
     * calling this method to ensure the returned offerings are applied with the latest subscriber attributes.
     *
     * This method is rate limited to 5 calls per minute. It will log a warning and return offerings cache when reached.
     *
     * - Parameter completion: A completion block called when attributes are synced and offerings are available.
     * Called immediately with cached offerings if rate limit reached. ``Offerings`` will be `nil` if an error occurred.
     *
     * #### Related Articles
     * -  [Targeting](https://docs.revenuecat.com/docs/targeting)
     */
    @objc func syncAttributesAndOfferingsIfNeeded(completion: @escaping (Offerings?, PublicError?) -> Void)

    /**
     * Syncs subscriber attributes and then fetches the configured offerings for this user. This method is intended to
     * be called when using Targeting Rules with Custom Attributes. Any subscriber attributes should be set before
     * calling this method to ensure the returned offerings are applied with the latest subscriber attributes.
     *
     * This method is rate limited to 5 calls per minute. It will log a warning and return offerings cache when reached.
     *
     * #### Related Articles
     * -  [Targeting](https://docs.revenuecat.com/docs/targeting)
     */
    @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *)
    func syncAttributesAndOfferingsIfNeeded() async throws -> Offerings?

    /**
     * Redeems a web purchase previously parsed from a deep link with ``Purchases/parseAsWebPurchaseRedemption(_:)``.
     *
     * - Parameter webPurchaseRedemption: WebPurchaseRedemption object previously parsed from
     * a URL using ``Purchases/parseAsWebPurchaseRedemption(_:)``
     * - Parameter completion: The completion block to be called with the updated CustomerInfo
     * on a successful redemption, or the error if not.
     * - Seealso: ``Purchases/redeemWebPurchase(_:)``
     */
    @objc func redeemWebPurchase(
        webPurchaseRedemption: WebPurchaseRedemption,
        completion: @escaping (CustomerInfo?, PublicError?) -> Void
    )

    /**
     * Fetches the virtual currencies for the current subscriber.
     *
     * - Parameter completion: The callback that is called when the request is complete with a ``VirtualCurrencies``
     * object containing the subscriber's virtual currencies.
     *
     * #### Related Articles
     * -  [Virtual Currencies](https://www.revenuecat.com/docs/offerings/virtual-currency)
     */
    @objc
    func getVirtualCurrencies(
        completion: @escaping @Sendable (VirtualCurrencies?, PublicError?) -> Void
    )

    /**
     * The currently cached ``VirtualCurrencies`` if one is available.
     * This is synchronous, and therefore useful for contexts where an app needs a `VirtualCurrencies`
     * right away without waiting for a callback, like a SwiftUI view.
     *
     * This allows initializing state to ensure that UI can be loaded from the very first frame.
     */
    var cachedVirtualCurrencies: VirtualCurrencies? { get }

    /**
     * Invalidates the cache for virtual currencies.
     *
     * This is useful for cases where a virtual currency's balance might have been updated
     * outside of the app, like if you decreased a user's balance from the user spending a virtual currency,
     * or if you increased the balance from your backend using the server APIs.
     *
     * #### Related Articles
     * -  [Virtual Currencies](https://www.revenuecat.com/docs/offerings/virtual-currency)
     */
    @objc
    func invalidateVirtualCurrenciesCache()

    // MARK: - Deprecated

    // swiftlint:disable missing_docs

    func setAttributes(_ attributes: [String: String])

    @available(*, deprecated)
    var allowSharingAppStoreAccount: Bool { get set }
    @available(*, deprecated)
    func setEmail(_ email: String?)
    @available(*, deprecated)
    func setPhoneNumber(_ phoneNumber: String?)
    @available(*, deprecated)
    func setDisplayName(_ displayName: String?)
    @available(*, deprecated)
    func setPushToken(_ pushToken: Data?)
    @available(*, deprecated)
    func setPushTokenString(_ pushToken: String?)
    @available(*, deprecated)
    func setAdjustID(_ adjustID: String?)
    @available(*, deprecated)
    func setAppsflyerID(_ appsflyerID: String?)
    @available(*, deprecated)
    func setFBAnonymousID(_ fbAnonymousID: String?)
    @available(*, deprecated)
    func setMparticleID(_ mparticleID: String?)
    @available(*, deprecated)
    func setOnesignalID(_ onesignalID: String?)
    @available(*, deprecated)
    func setMediaSource(_ mediaSource: String?)
    @available(*, deprecated)
    func setCampaign(_ campaign: String?)
    @available(*, deprecated)
    func setAdGroup(_ adGroup: String?)
    @available(*, deprecated)
    func setAd(_ value: String?)
    @available(*, deprecated)
    func setKeyword(_ keyword: String?)
    @available(*, deprecated)
    func setCreative(_ creative: String?)
    @available(*, deprecated)
    func setCleverTapID(_ cleverTapID: String?)
    @available(*, deprecated)
    func setMixpanelDistinctID(_ mixpanelDistinctID: String?)
    @available(*, deprecated)
    func setFirebaseAppInstanceID(_ firebaseAppInstanceID: String?)
    @available(*, deprecated)
    func collectDeviceIdentifiers()
    @available(*, deprecated)
    @objc(params:withCompletion:)
    func purchaseWithParams(_ params: PurchaseParams, completion: @escaping PurchaseCompletedBlock)

    // swiftlint:enable missing_docs

    #endif

    /** Whether transactions should be finished automatically. `true` by default.
     * - Warning: Setting this value to `false` will prevent the SDK from finishing transactions.
     * In this case, you *must* finish transactions in your app, otherwise they will remain in the queue and
     * will turn up every time the app is opened.
     * More information on finishing transactions manually [is available here](https://rev.cat/finish-transactions).
     */
    @available(*, deprecated, message: "Use purchasesAreCompletedBy instead.")
    var finishTransactions: Bool { get set }

}

/// Interface for ``Purchases``'s `Swift`-only methods.
public protocol PurchasesSwiftType: AnyObject {

    /// Returns an `AsyncStream` of ``CustomerInfo`` changes, starting from the last known value.
    ///
    /// #### Related Symbols
    /// - ``PurchasesDelegate/purchases(_:receivedUpdated:)``
    /// - ``Purchases/customerInfo(fetchPolicy:)``
    ///
    /// #### Example:
    /// ```swift
    /// for await customerInfo in Purchases.shared.customerInfoStream {
    ///   // this gets called whenever new CustomerInfo is available
    ///   let entitlements = customerInfo.entitlements
    ///   ...
    /// }
    /// ```
    ///
    /// - Note: An alternative way of getting ``CustomerInfo`` updates
    /// is using ``PurchasesDelegate/purchases(_:receivedUpdated:)``.
    /// - Important: this method is not thread-safe.
    var customerInfoStream: AsyncStream<CustomerInfo> { get }

    #if os(iOS) || VISION_OS

    /**
     * Presents a refund request sheet in the current window scene for
     * the latest transaction associated with the `productID`
     *
     * - Parameter productID: The `productID` to begin a refund request for.
     * - Parameter completion: A completion block that is called when the ``RefundRequestStatus`` is returned.
     * Keep in mind the status could be ``RefundRequestStatus/userCancelled``
     * If the request was unsuccessful, no active entitlements could be found for the user,
     * or multiple active entitlements were found for the user, an `Error` will be thrown.
     */
    @available(iOS 15.0, *)
    @available(macOS, unavailable)
    @available(watchOS, unavailable)
    @available(tvOS, unavailable)
    func beginRefundRequest(
        forProduct productID: String,
        completion: @escaping (Result<RefundRequestStatus, PublicError>) -> Void
    )

    /**
     * Presents a refund request sheet in the current window scene for
     * the latest transaction associated with the entitlement ID.
     *
     * - Parameter entitlementID: The entitlementID to begin a refund request for.
     * - Parameter completion: A completion block that is called when the ``RefundRequestStatus`` is returned.
     * Keep in mind the status could be ``RefundRequestStatus/userCancelled``
     * If the request was unsuccessful, no active entitlements could be found for the user,
     * or multiple active entitlements were found for the user, an `Error` will be thrown.
     */
    @available(iOS 15.0, *)
    @available(macOS, unavailable)
    @available(watchOS, unavailable)
    @available(tvOS, unavailable)
    func beginRefundRequest(
        forEntitlement entitlementID: String,
        completion: @escaping (Result<RefundRequestStatus, PublicError>) -> Void
    )

    /**
     * Presents a refund request sheet in the current window scene for
     * the latest transaction associated with the active entitlement.
     *
     * - Parameter completion: A completion block that is called when the ``RefundRequestStatus`` is returned.
     * Keep in mind the status could be ``RefundRequestStatus/userCancelled``
     * If the request was unsuccessful, no active entitlements could be found for the user,
     * or multiple active entitlements were found for the user, an `Error` will be thrown.
     *
     * - Important: This method should only be used if your user can only
     * have a single active entitlement at a given time. If a user could have more than one entitlement at a time,
     * use ``beginRefundRequest(forEntitlement:completion:)`` instead.
     */
    @available(iOS 15.0, *)
    @available(macOS, unavailable)
    @available(watchOS, unavailable)
    @available(tvOS, unavailable)
    func beginRefundRequestForActiveEntitlement(
        completion: @escaping (Result<RefundRequestStatus, PublicError>) -> Void
    )

    #endif

    #if os(iOS) || targetEnvironment(macCatalyst) || VISION_OS

    /**
     * Displays the specified store in-app message types to the user if there are any available to be shown.
     * - Important: This should only be used if you disabled these messages from showing automatically
     * during SDK configuration using ``Configuration/Builder/with(showStoreMessagesAutomatically:)``
     * ### Related Symbols
     * - ``Configuration/Builder/with(showStoreMessagesAutomatically:)``
     */
    @available(iOS 16.0, *)
    @available(macOS, unavailable)
    @available(watchOS, unavailable)
    @available(tvOS, unavailable)
    func showStoreMessages(for types: Set<StoreMessageType>) async

    #endif

    /**
     * Use this method only if you already have your own IAP implementation using StoreKit 2 and want to use
     * RevenueCat's backend. If you are using StoreKit 1 for your implementation, you do not need this method.
     *
     * You only need to use this method with *new* purchases. Subscription updates are observed automatically.
     *
     * #### Example:
     *
     * ```swift
     * // Fetch and purchase the product
     * let product = try await StoreKit.Product.products(for: ["my_product_id"]).first
     * guard let product = product else { return }
     * let result = try await product.purchase()
     * // Let RevenueCat handle the transaction result
     * _ = try await Purchases.shared.recordPurchase(result)
     * // Handle the result and finish the transaction
     * switch result {
     * case .success(let verification):
     *     switch verification {
     *     case .unverified(_, _):
     *         break
     *     case .verified(let transaction):
     *         // If the purchase was successful and verified, finish the transaction
     *         await transaction.finish()
     *     }
     * case .userCancelled:
     *     break
     * case .pending:
     *     break
     * @unknown default:
     *     break
     * }
     * ```
     *
     * - Warning: You need to finish the transaction yourself after calling this method.
     *
     * - Parameter purchaseResult: The `StoreKit.Product.PurchaseResult` of the product that was just purchased.
     *
     * - Throws: An error of type ``ErrorCode`` is thrown if a failure occurs while handling the purchase.
     *
     * - Returns: A ``StoreTransaction`` if there was a transacton found and handled for the provided product ID.
     *
     * - Important: This should only be used if you are processing transactions directly within your app, configuring
     * the SDK by passing ``PurchasesAreCompletedBy/myApp`` to `purchasesAreCompletedBy`: in
     * ``Configuration/Builder/with(purchasesAreCompletedBy:storeKitVersion:)``
     */
    @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
    func recordPurchase(
        _ purchaseResult: StoreKit.Product.PurchaseResult
    ) async throws -> StoreTransaction?

    /**
     * Redeems a web purchase previously parsed from a deep link with ``Purchases/parseAsWebPurchaseRedemption(_:)``
     *
     * - Parameter webPurchaseRedemption: Deep link previously parsed from a
     * URL using ``Purchases/parseAsWebPurchaseRedemption(_:)``
     */
    func redeemWebPurchase(
        _ webPurchaseRedemption: WebPurchaseRedemption
    ) async -> WebPurchaseRedemptionResult

    #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION
    /**
     * Returns the win-back offers that the subscriber is eligible for on the provided product.
     *
     * - Parameter product: The product to check for eligible win-back offers.
     * - Returns: The win-back offers on the given product that a subscriber is eligible for.
     * - Important: Win-back offers are only supported when the SDK is running with StoreKit 2 enabled.
     */
    @available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
    func eligibleWinBackOffers(
        forProduct product: StoreProduct
    ) async throws -> [WinBackOffer]

    /**
     * Returns the win-back offers that the subscriber is eligible for on the provided package.
     *
     * - Parameter package: The package to check for eligible win-back offers.
     * - Returns: The win-back offers on the given product that a subscriber is eligible for.
     * - Important: Win-back offers are only supported when the SDK is running with StoreKit 2 enabled.
     */
    @available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
    func eligibleWinBackOffers(
        forPackage package: Package
    ) async throws -> [WinBackOffer]

    /**
     * Fetches the virtual currencies for the current subscriber.
     *
     * - Returns: The ``VirtualCurrencies`` object containing the virtual currencies for the subscriber.
     *
     * #### Related Articles
     * -  [Virtual Currencies](https://www.revenuecat.com/docs/offerings/virtual-currency)
     */
    func virtualCurrencies() async throws -> VirtualCurrencies
    #endif
}

// MARK: -

/// Interface for ``Purchases``'s internal-only methods.
internal protocol InternalPurchasesType: AnyObject {

    /// Performs an unauthenticated request to the API to verify connectivity.
    /// - Throws: `PublicError` if request failed.
    func healthRequest(signatureVerification: Bool) async throws

    #if DEBUG
    /// Requests an in-depth report of the SDK's configuration from the server.
    /// - Throws: A `BackendError` if the request fails due to an invalid API key or connectivity issues.
    /// - Returns: A health report containing all checks performed on the server and their status.
    func healthReport() async -> PurchasesDiagnostics.SDKHealthReport
    #endif

    func offerings(fetchPolicy: OfferingsManager.FetchPolicy) async throws -> Offerings

    @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
    func productEntitlementMapping() async throws -> ProductEntitlementMapping

    var responseVerificationMode: Signing.ResponseVerificationMode { get }

}
